﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using AutoMapper;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Serialization;
using Project.Core.Api.AutoMapper;
using Project.Core.DAL;
using Project.Core.IRepository;
using Project.Core.IServices;
using Project.Core.Repository;
using Project.Core.Services;
using Project.Core.Token;
using Project.Core.Token.Token;
using Swashbuckle.AspNetCore.Swagger;

namespace Project.Core.Api
{
    /// <summary>
    /// 入口
    /// </summary>
    public class Startup
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="configuration"></param>
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
            _mapperConfiguration = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile<AutoMapperConfig>();
            });
        }

        /// <summary>
        /// Configuration 属性
        /// </summary>
        public IConfiguration Configuration { get; }

        private MapperConfiguration _mapperConfiguration { get; set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        /// <summary>
        /// 注册
        /// </summary>
        /// <param name="services"></param>
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            #region Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
                {
                    Version = "v0.1.0",
                    Title = "JYProject.API",
                    Description = "接口说明文档",
                    TermsOfService = "None",
                    Contact = new Swashbuckle.AspNetCore.Swagger.Contact { Name = "", Email = "", Url = "" }
                });

                // 系统启动的时候，读取注释
                // .net core 2.0 获取basePath 的方式
                // var basePath = PlatformServices.Default.Application.ApplicationBasePath;
                // .net core 2.1 获取basePath 的方式
                var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;
                var xmlPath = Path.Combine(basePath, "Project.Core.Api.xml");
                c.IncludeXmlComments(xmlPath, true);  //默认的第二个参数是false，这个是controller的注释，记得修改

                var xmlModelPath = Path.Combine(basePath, "Project.Core.Model.xml"); //这个就是Model层的xml文件名
                c.IncludeXmlComments(xmlModelPath);


                #region Token绑定到ConfigureServices
                // 添加Header验证信息
                var security = new Dictionary<string, IEnumerable<string>> { { "Project.Core.Api", new string[] { } }, };
                c.AddSecurityRequirement(security);
                // 方案名称"Project.Core.Api"可自定义，上下一致即可
                c.AddSecurityDefinition("Project.Core.Api", new ApiKeyScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入{token}\"",
                    Name = "Authorization",//jwt默认的参数名称
                    In = "header",//jwt默认存放Authorization信息的位置(请求头中)
                    Type = "apiKey"
                });
                #endregion
            });
            #endregion

            #region 缓存
            services.AddSingleton<IMemoryCache>(factory =>
            {
                var cache = new MemoryCache(new MemoryCacheOptions());
                return cache;
            });
            #endregion

            #region token 认证

            //将appsettings.json中的JwtSettings部分文件读取到JwtSettings中，这是给其他地方用的
            services.Configure<JwtSettings>(Configuration.GetSection("JwtSettings"));

            var jwtSettings = new JwtSettings();

            Configuration.Bind("JwtSettings", jwtSettings);

            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(o =>
            {
                o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
                {
                    // 是否验证Issuer
                    //ValidateIssuer = true,
                    //// 是否验证Audience
                    //ValidateAudience = true,
                    //// 是否验证失效时间
                    //ValidateLifetime = true,
                    //// 是否验证SecurityKey
                    //ValidateIssuerSigningKey = true,
                    // Token颁发机构
                    ValidIssuer = jwtSettings.Issuer,
                    // 颁发给谁
                    ValidAudience = jwtSettings.Audience,
                    // 这里的key要进行加密，需要引用Microsoft.IdentityModel.Tokens
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey))
                    // ValidateIssuerSigningKey=true,
                    // 是否验证Token有效期，使用当前时间与Token的Claims中的NotBefore和Expires对比
                    // ValidateLifetime=true,
                    // 允许的服务器时间偏移量
                    // ClockSkew=TimeSpan.Zero
                };
            });

            services.AddAuthorization(options =>
            {
                options.AddPolicy("Client", policy => policy.RequireRole("Client").Build());
                options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build());
                options.AddPolicy("AdminOrClient", policy => policy.RequireRole("Admin", "Client").Build());
            });
            #endregion

            #region 获取数据库连接字符串
            BaseDBConfig.ConnectionString = Configuration.GetSection("AppSettings:mysql").Value;
            // MongoDBConfig.ConnectionString = Configuration.GetSection("AppSettings:mongodb").Value;
            #endregion

            #region AutoMapper
            // Mapper.Initialize(cfg => cfg.CreateMap<PermissionAddDto, UserPermission>());
            // services.AddAutoMapper();
            services.AddSingleton<IMapper>(sp => _mapperConfiguration.CreateMapper());
            #endregion

            #region 将Redis分布式缓存服务添加到服务中
            //将Redis分布式缓存服务添加到服务中
            //services.AddDistributedRedisCache(options =>
            //{
            //    //用于连接Redis的配置  Configuration.GetConnectionString("RedisConnectionString")读取配置信息的串
            //    options.Configuration = "localhost";// Configuration.GetConnectionString("RedisConnectionString");
            //    //Redis实例名RedisDistributedCache
            //    options.InstanceName = "RedisDistributedCache";
            //});
            #endregion

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            // 返回首字母大写
            //services.AddMvc().AddJsonOptions(options =>
            //{
            //    options.SerializerSettings.ContractResolver = new DefaultContractResolver();
            //});

            #region 通过反射将Project.Core.Services和Project.Core.Repository两个程序集的全部方法注入
            var builder = new ContainerBuilder();

            // 注册要通过反射创建的组件
            var assemblysServices = Assembly.Load("Project.Core.Services");

            // 指定已扫描程序集中的类型注册为提供所有其实现的接口。
            builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();

            var assemblysRepository = Assembly.Load("Project.Core.Repository");

            builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces();

            // 将services填充到Autofac容器生成器中
            builder.Populate(services);

            var ApplicationContainer = builder.Build();

            // 第三方IOC接管 core内置DI容器
            return new AutofacServiceProvider(ApplicationContainer);
            #endregion
        }

        /// <summary>
        /// Configure
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            //if (env.IsDevelopment())
            //{
            app.UseDeveloperExceptionPage();

            #region Swagger
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1");
                // 如果想直接在域名的根目录直接加载 swagger 比如访问：localhost:8001 就能访问，可以这样设置：
                c.RoutePrefix = "swagger";//路径配置，设置为空，表示直接访问该文件
            });
            #endregion
            //}

            #region TokenAuth

            // 在之前的方法中，我们用到了中间件 app.UseMiddleware<JwtTokenAuth>(); 
            // 当然也是可以的，只不过授权的时候写的不全，才导致验证的时候有效时间没办法识别，因为我们在生成Token的时候，已经配置好了 claim 声明，
            // 所以直接调用官方的验证即可。这样的好处是，我们也不用去判断  Headers 是否包含 Authorization 的操作；
            // app.UseMiddleware<TokenAuth>();
            app.UseAuthentication();
            #endregion

            // Mappings.RegisterMappings();

            app.UseMvc();

            app.UseStaticFiles();   // 用户访问wwwroot下的文件
            //app.UseStaticFiles(new StaticFileOptions()//后台的前台页面暂时放在wwwroot目录下，因为可以直接输入域名访问，admin目录需要输入/admin/index.html,才可以访问，可以用于定义一些静态资源的访问，所以暂时先注释掉
            //{
            //    FileProvider = new PhysicalFileProvider
            //    (
            //       Path.Combine(Directory.GetCurrentDirectory(), @"admin/")),   //实际目录地址
            //    RequestPath = new Microsoft.AspNetCore.Http.PathString("/admin"),  //用户访问地址
            //});

        }
    }
}
